package;

import massive.munit.Assert;
import hxm.Monad;

class ArrayMonadTest
{
	static var flashSafeIntNull = #if flash9 0 #else null #end;
	
	public function new()
	{
	}
	
	@Before
	public function setUp():Void
	{
		
	}
	
	@After
	public function tearDown():Void
	{
		
	}

	@Test
	public function bindYield()
	{
		var r = Monad.exec( {
			x <= [1, 2, 3];
			return x;
		});
		
		Assert.areEqual(3, r.length);
		Assert.areEqual(1, r[0]);
		Assert.areEqual(2, r[1]);
		Assert.areEqual(3, r[2]);
	}
	
	@Test
	public function yieldInto()
	{
		var r = Monad.exec( {
			x <= [1];
			y <= return 9;
			return y * x;
		});
		
		Assert.areEqual(1, r.length);
		Assert.areEqual(9, r[0]);
	}
	
	@Test
	public function zero()
	{
		var r = Monad.exec( {
			x <= [];
			return x;
		});
		
		Assert.areEqual(0, r.length);
	}	
	
	@Test
	public function zeroCombined()
	{
		var r = Monad.exec( {
			x <= [1, 2, 3];
			y <= [];
			return x;
		});
		
		Assert.areEqual(0, r.length);
	}
	
	@Test
	public function onlyNull()
	{
		var r = Monad.exec( {
			Array;
			x <= null;
			return x;
		});
		
		Assert.areEqual(0, r.length);
	}	

	@Test
	public function twoArrays()
	{
		var r = Monad.exec( {
			x <= [1, 2];
			y <= [3, 4];
			return x + y;
		});
		
		Assert.areEqual(4, r.length);
		Assert.areEqual(4, r[0]);
		Assert.areEqual(5, r[1]);
		Assert.areEqual(5, r[2]);
		Assert.areEqual(6, r[3]);
	}
	
	
	@Test
	public function oneNull()
	{
		var r = Monad.exec( {
			x <= [1, 2];
			y <= null;
			return x + y;
		});
		
		Assert.areEqual(0, r.length);
	}

	@Test
	public function innerScope()
	{
		var r = Monad.exec( {
			Array;
			y <= {
				z <= [3];
				return z;
			}
			return y;
		});
		
		Assert.isTrue(Std.is(r, Array));
		Assert.areEqual(1, r.length);
		Assert.isTrue(Std.is(r[0], Array));
		Assert.areEqual(3, r[0][0]);
	}
	

	@Test
	public function nestedScope()
	{
		var r = Monad.exec( {
			x <= [1, 2];
			y <= {
				z <= [x];
				return z;
			}
			return y[0] * x;
		});
		
		Assert.isTrue(Std.is(r, Array));
		Assert.areEqual(2, r.length);
		Assert.areEqual(1, r[0]);
		Assert.areEqual(4, r[1]);
	}

	@Test
	public function exprRet()
	{
		var r = Monad.exec( {
			x <= ["Hello", "World"];
			return x.substr(0, 2);
		});
		Assert.isTrue(Std.is(r, Array));
		Assert.areEqual(2, r.length);
		Assert.areEqual("He", r[0]);
		Assert.areEqual("Wo", r[1]);
	}
	

	@Test
	public function iterableFallback()
	{
		var k:Iterable<Float> = [3.0, 5.0];

		var r = Monad.exec( {
			x <= [1.0, 2.0];	
			it <= k;
			
			return it * x;
		});
		
		// TODO: Float equality testing is bad...
		Assert.isTrue(Std.is(r, List));
		var l = cast(r, List<Dynamic>);
		Assert.areEqual(3.0, l.pop());
		Assert.areEqual(5.0, l.pop());
		Assert.areEqual(6.0, l.pop());
		Assert.areEqual(10.0, l.pop());
	}
	
	@Test
	public function listUnification()
	{
		var l = new List();
		l.add("Hello");
		l.add("World");
		
		var r = Monad.exec( {
			x <= [1, 2];
			l <= l;
			return l + x;
		});
		
		Assert.isTrue(Std.is(r, List));
		var l = cast(r, List<Dynamic>);
		Assert.areEqual(4, l.length);
		Assert.areEqual("Hello1", l.pop());
		Assert.areEqual("World1", l.pop());
		Assert.areEqual("Hello2", l.pop());
		Assert.areEqual("World2", l.pop());
	}
	
	@Test
	public function guard()
	{
		var r = Monad.exec( {
			x <= [1, 2];
			y <= [1, 2];
			guard(x + y != 3);
			return { x:x, y:y };
		});
		
		Assert.areEqual(2, r.length);
		Assert.areEqual(1, r[0].x);
		Assert.areEqual(1, r[0].y);
		Assert.areEqual(2, r[1].x);
		Assert.areEqual(2, r[1].y);
	}
}